package com.lyncs.ods.task;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.extra.mail.MailUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.lyncs.ods.constant.LyncsOdsConstant;
import com.lyncs.ods.modules.company.model.CompanyContactInfo;
import com.lyncs.ods.modules.company.model.CompanyInfo;
import com.lyncs.ods.modules.company.model.ContactInfo;
import com.lyncs.ods.modules.company.service.CompanyContactInfoService;
import com.lyncs.ods.modules.company.service.CompanyInfoService;
import com.lyncs.ods.modules.msg.service.MessageService;
import com.lyncs.ods.modules.setting.model.ShortLinkInfo;
import com.lyncs.ods.modules.setting.service.ShortLinkInfoService;
import com.lyncs.ods.modules.txn.model.OrderInfo;
import com.lyncs.ods.modules.txn.model.SettlementInfo;
import com.lyncs.ods.modules.txn.model.TxnCommonInfo;
import com.lyncs.ods.modules.txn.service.OrderInfoService;
import com.lyncs.ods.modules.txn.service.SettlementInfoService;
import com.lyncs.ods.modules.user.model.UserInfo;
import com.lyncs.ods.modules.user.service.CompanyUserRelationService;
import com.lyncs.ods.properties.EmailConfigSetting;
import com.lyncs.ods.properties.SmsConfigSetting;
import com.lyncs.ods.utils.AliUtils;
import com.lyncs.ods.utils.SmsUtils;
import com.lyncs.ods.utils.SpringUtils;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.PropertyPlaceholderHelper;

import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 订单滞留通知
 *
 * @author alex
 * @date 2022/5/31 23:35
 * @description
 */
@Component
@Slf4j
public class NotifyTask {

    @Value("${ali.accessKey}")
    private String accessKey;
    @Value("${ali.accessSecret}")
    private String accessSecret;

    @Autowired
    private OrderInfoService orderInfoService;
    @Autowired
    private SettlementInfoService settlementInfoService;
    @Autowired
    private CompanyInfoService companyInfoService;
    @Autowired
    private CompanyUserRelationService companyUserRelationService;
    @Autowired
    private CompanyContactInfoService companyContactInfoService;
    @Autowired
    private ShortLinkInfoService shortLinkInfoService;

    private final static PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER = new PropertyPlaceholderHelper("{", "}");

    public static final Integer BEFORE_DAY = -2;
    public static final Integer AFTER_DAY = 1;

    @Scheduled(cron = "0 0 10 ? * *", zone = "GMT+8:00")
    public void run() {
        LambdaQueryChainWrapper<OrderInfo> orderWrapper = orderInfoService.lambdaQuery().setEntityClass(OrderInfo.class);
        run(LyncsOdsConstant.TxnType.ORDER, orderWrapper, List.of(LyncsOdsConstant.OrderStatus.INIT.getKey(), LyncsOdsConstant.OrderStatus.PARTIAL_DELIVERY.getKey()));
        LambdaQueryChainWrapper<SettlementInfo> settleWrapper = settlementInfoService.lambdaQuery().setEntityClass(SettlementInfo.class);
        run(LyncsOdsConstant.TxnType.SETTLEMENT, settleWrapper, List.of(LyncsOdsConstant.SettlementStatus.INIT.getKey()));
    }

    private <T extends TxnCommonInfo> void run(LyncsOdsConstant.TxnType type, LambdaQueryChainWrapper<T> wrapper, List<Integer> status) {
        LocalDate startDate = LocalDate.now().plusDays(BEFORE_DAY);
        LocalDate endDate = LocalDate.now().plusDays(AFTER_DAY);
        List<T> list = wrapper.ge(T::getStartDate, startDate).le(T::getStartDate, endDate).in(T::getStatus, status).list();
        if (CollectionUtil.isEmpty(list)) {
            log.info("no handler ticket of:{}, between {} and {}", type.getDesc(), startDate, endDate);
            return;
        }
        Set<Long> companyIds = list.stream().flatMap(t -> Stream.of(t.getBuyerId(), t.getSellerId(), t.getCreatorCompanyId())).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(companyIds)) {
            log.error("发送到期提醒失败，企业未找到");
            return;
        }
        List<NotifyInfo> notifyInfos = new ArrayList<>();
        List<Long> companyIdList = new ArrayList<>(companyIds);

        List<CompanyInfo> companyInfos = companyInfoService.listByIds(companyIdList);
        Map<Long, CompanyInfo> companyInfoMap = companyInfos.stream().collect(Collectors.toMap(CompanyInfo::getId, o -> o));

        Map<Long, List<UserInfo>> companyUserInfoMap = companyUserRelationService.getCompanyUserInfoMap(companyIdList);
        Map<Long, Map<Long, CompanyContactInfo>> txnConcatCompanyMap = companyContactInfoService.getTxnConcatMap(list);

        for (T t : list) {
            Long creatorCompanyId = t.getCreatorCompanyId();
            Long sellerId = t.getSellerId();
            Long buyerId = t.getBuyerId();

            if (!creatorCompanyId.equals(sellerId)) {
                NotifyInfo notifyInfo = buildNotifyInfo(t.getId(), type, companyInfoMap, txnConcatCompanyMap, companyUserInfoMap, sellerId, buyerId, t.getStartDate());
                notifyInfos.add(notifyInfo);
            }
            if (!creatorCompanyId.equals(buyerId)) {
                NotifyInfo notifyInfo = buildNotifyInfo(t.getId(), type, companyInfoMap, txnConcatCompanyMap, companyUserInfoMap, buyerId, sellerId, t.getStartDate());
                notifyInfos.add(notifyInfo);
            }
        }
        log.info("开始处理消息提醒....");
        notifyInfos.forEach(this::handlerOne);
        log.info("处理消息提醒结束....");
    }

    private void handlerOne(NotifyInfo notifyInfo) {
        if (Objects.isNull(notifyInfo)) {
            return;
        }
        log.info("处理单次消息提醒:{}", JSONObject.toJSONString(notifyInfo));
        LocalDate today = LocalDate.now();
        SmsConfigSetting smsConfigSetting = null;
        EmailConfigSetting emailConfigSetting = null;
        if (LyncsOdsConstant.TxnType.ORDER.equals(notifyInfo.getType())) {
            if (notifyInfo.getExpiredDate().isBefore(today)) {
                smsConfigSetting = SmsConfigSetting.DELIVER_EXPIRED;
                emailConfigSetting = EmailConfigSetting.DELIVER_EXPIRED;
            } else {
                smsConfigSetting = SmsConfigSetting.DELIVER_EXPIRING;
                emailConfigSetting = EmailConfigSetting.DELIVER_EXPIRING;
            }
        }
        if (LyncsOdsConstant.TxnType.SETTLEMENT.equals(notifyInfo.getType())) {
            if (notifyInfo.getExpiredDate().isBefore(today)) {
                smsConfigSetting = SmsConfigSetting.SETTLEMENT_EXPIRED;
                emailConfigSetting = EmailConfigSetting.SETTLEMENT_EXPIRED;
            } else {
                smsConfigSetting = SmsConfigSetting.SETTLEMENT_EXPIRING;
                emailConfigSetting = EmailConfigSetting.SETTLEMENT_EXPIRING;
            }
        }
        ShortLinkInfo linkInfo = shortLinkInfoService.generateShortLink(notifyInfo.getType(), notifyInfo.getTicketId(), notifyInfo.getCompanyInfo().getId(), notifyInfo.getInvitedCompanyInfo().getId());
        if (smsConfigSetting != null && CollectionUtils.isNotEmpty(notifyInfo.getSmsTos())) {
            if (SpringUtils.isProd()) {
                Map<String, String> param = new HashMap<>() {{
                    put("companyName", notifyInfo.getCompanyInfo().getShortName());
                    put("ticketName", notifyInfo.getType().getDesc());
                    put("code", linkInfo.getKey());
                }};
                String smsContent = SmsUtils.buildContent(smsConfigSetting, notifyInfo.getCompanyInfo().getShortName(), linkInfo.getDeeplink());
                String resp = AliUtils.sendSms(accessKey, accessSecret, notifyInfo.getSmsTos().stream().distinct().collect(Collectors.joining(LyncsOdsConstant.Common.COMMA)), smsConfigSetting.getSign(), smsConfigSetting.getTemplateCode(), JSON.toJSONString(param), null, null, null);
                log.info("发送提醒短信，ID:{}，内容：{}，短信响应结果：{}", notifyInfo.getTicketId(), smsContent, resp);
            } else {
                log.warn("非生产环境不发送短信提醒...");
            }
        }

        if (emailConfigSetting != null && CollectionUtils.isNotEmpty(notifyInfo.getEmailTos())) {
            String htmlContent = getHtmlContent(emailConfigSetting);
            if (StringUtils.isEmpty(htmlContent)) {
                log.error("获取邮件内容失败，单据ID:{}", notifyInfo.getTicketId());
                return;
            }
            CompanyInfo notifyCompany = notifyInfo.getCompanyInfo();
            CompanyInfo companyInfo = notifyInfo.getInvitedCompanyInfo();
            Map<String, String> param = new HashMap<>() {{
                put("companyLogo", companyInfo.getAvatarUrl() == null ? "https://welynk.cn/images/s/c1.png" : companyInfo.getAvatarUrl());
                put("companyShortName", companyInfo.getShortName());
                put("companyName", companyInfo.getName() == null ? "" : companyInfo.getName());
                put("companySlogan", companyInfo.getSlogan() == null ? "" : companyInfo.getSlogan());
                put("shareLink", linkInfo.getDeeplink());
                put("nickname", notifyCompany.getShortName());
            }};
            String content = PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(htmlContent, param::get);
            String resp = MailUtil.send(notifyInfo.getEmailTos().stream().distinct().collect(Collectors.joining(LyncsOdsConstant.Common.COMMA)),
                    emailConfigSetting.getName(),
                    content, true);
            log.info("发送提醒邮件，ID:{}，内容：{}，邮件响应结果：{}", notifyInfo.getTicketId(), content, resp);
        }
    }


    private NotifyInfo buildNotifyInfo(Long ticketId, LyncsOdsConstant.TxnType type,
                                       Map<Long, CompanyInfo> companyInfoMap,
                                       Map<Long, Map<Long, CompanyContactInfo>> txnConcatCompanyMap,
                                       Map<Long, List<UserInfo>> companyUserInfoMap,
                                       Long companyId, Long relationId, LocalDate expiredDate) {
        if (!companyInfoMap.containsKey(companyId) || !companyInfoMap.containsKey(relationId)) {
            log.info("交易双方企业不存在，单据ID:{}", ticketId);
            return null;
        }
        //companyInfo
        CompanyInfo companyInfo = companyInfoMap.get(relationId);
        //contactInfo
        Map<Long, CompanyContactInfo> companyContactInfoMap = txnConcatCompanyMap.get(companyId);
        //没有好友关系的单据不处理
        if (MapUtils.isEmpty(companyContactInfoMap) || !companyContactInfoMap.containsKey(relationId)) {
            log.info("交易双方没有建立好友关系，单据ID:{}", ticketId);
            return null;
        }
        Map<Long, CompanyContactInfo> invitedContactInfoMap = txnConcatCompanyMap.get(relationId);
        //没有好友关系的单据不处理
        if (MapUtils.isEmpty(invitedContactInfoMap) || !invitedContactInfoMap.containsKey(companyId)) {
            log.info("交易双方没有建立好友关系【opposite】，单据ID:{}", ticketId);
            return null;
        }
        CompanyContactInfo companyContactInfo = companyContactInfoMap.get(relationId);
        List<String> emailTos = new ArrayList<>();
        List<String> smsTos = new ArrayList<>();
        String wcId = null;
        if (companyContactInfo != null && companyContactInfo.getRelationId().equals(companyContactInfo.getInitCompanyId())) {
            companyInfo = new CompanyInfo().setId(companyContactInfo.getRelationId()).setShortName(companyContactInfo.getNickname());
            ContactInfo contactInfo = companyContactInfo.getContactDetail();
            String email = contactInfo.getEmail();
            if (StringUtils.isNotEmpty(email)) {
                emailTos.add(email);
            }
            String phones = contactInfo.getTelephone();
            if (StringUtils.isNotEmpty(phones)) {
                smsTos.addAll(Arrays.asList(phones.split(LyncsOdsConstant.Common.COMMA)));
            }
            wcId = companyContactInfo.getWcId();
        }
        List<UserInfo> userInfos = companyUserInfoMap.get(relationId);
        if (CollectionUtils.isNotEmpty(userInfos)) {
            for (UserInfo userInfo : userInfos) {
                if (StringUtils.isNotEmpty(userInfo.getEmail())) {
                    emailTos.add(userInfo.getEmail());
                }
                if (StringUtils.isNotEmpty(userInfo.getPhone())) {
                    smsTos.add(userInfo.getPhone());
                }
            }
        }
        if (CollectionUtils.isEmpty(emailTos) && CollectionUtils.isEmpty(smsTos)) {
            log.info("未配置邮箱和短信，单据ID:{}", ticketId);
            return null;
        }
        CompanyInfo invitedCompanyInfo = companyInfoMap.get(relationId);
        CompanyContactInfo companyContactInfo1 = invitedContactInfoMap.get(companyId);
        companyInfo.setShortName(companyContactInfo1.getNickname());
        return new NotifyInfo().setType(type).setTicketId(ticketId).setExpiredDate(expiredDate).setCompanyInfo(companyInfo).setEmailTos(emailTos).setSmsTos(smsTos).setWcId(wcId).setInvitedCompanyInfo(invitedCompanyInfo);
    }


    @Data
    @Accessors(chain = true)
    static class NotifyInfo {
        /**
         * 到期时间
         */
        private LocalDate expiredDate;
        /**
         * 单据ID
         */
        private Long ticketId;
        /**
         * 单据类型
         */
        private LyncsOdsConstant.TxnType type;
        /**
         * 发送方企业信息
         */
        private CompanyInfo companyInfo;
        /**
         * 接收方企业信息
         */
        private CompanyInfo invitedCompanyInfo;
        /**
         * 邮箱
         */
        private List<String> emailTos;
        /**
         * 手机号码
         */
        private List<String> smsTos;
        /**
         * 群聊ID
         */
        private String wcId;
    }

    private String getHtmlContent(EmailConfigSetting configSetting) {
        try {
            ClassPathResource classPathResource = new ClassPathResource(configSetting.getPath());
            InputStream inputStream = classPathResource.getInputStream();
            return new String(inputStream.readAllBytes());
        } catch (IOException e) {
            log.info("获取邮件内容失败", e);
        }
        return null;
    }
}
